#include "shmipc/core/shared_memory.h"

#include <errno.h>
#include <string.h>
#include <unistd.h>
#include <sys/mman.h>
#include <sys/types.h>
#include <fcntl.h>
#include <sys/stat.h>

namespace shmipc::core {
struct SharedMemory_t {
    bool is_owner = false;
    int32_t fd = -1;
    uint32_t size = 0;
    void* buf = nullptr;
    std::string name;
};

XResult<SharedMemory> ShmAlloc(const std::string& name, uint32_t size) {
    int32_t fd = shm_open(name.c_str(), O_CREAT | O_RDWR | O_EXCL, S_IRUSR | S_IWUSR);
    if (fd < 0){
        printf("open shm for: %s, failed(err: %s)\n", name.c_str(), strerror(errno));
        return XResult<SharedMemory>::FromError(XErrC::AlreadyExist);
    }

    if (ftruncate(fd, size) < 0) {
        printf("open shm for: %s, with size(%u), failed(err: %s)\n", name.c_str(), size, strerror(errno)); 
        close(fd);
        shm_unlink(name.c_str());
        return XResult<SharedMemory>::FromError(XErrC::InvalidLength);
    }

    void* addr = mmap(NULL, size, PROT_READ|PROT_WRITE, MAP_SHARED, fd, 0);
    if (!addr) {
        printf("mmap for shm: %s, with size(%u), failed(err:%s)\n", name.c_str(), size, strerror(errno));
        close(fd);
        shm_unlink(name.c_str());
        return XResult<SharedMemory>::FromError(XErrC::InsufficientReousces);;
    }

    SharedMemory_t* sbuf = new SharedMemory_t;
    sbuf->fd = fd;
    sbuf->buf = addr;
    sbuf->size = size;
    sbuf->name = name;
    sbuf->is_owner = true;

    return sbuf;    
}


XResult<SharedMemory> ShmAttach(const std::string& name) {
    int32_t fd = shm_open(name.c_str(), O_RDWR, 0);
    if (fd < 0){
        printf("open for shm: %s, failed(err:%s)\n", name.c_str(), strerror(errno));
        return XResult<SharedMemory>::FromError(XErrC::NotFound);
    }

    struct stat statbuf;
    if (fstat(fd, &statbuf) < 0) {
        printf("get size for shm: %s, failed(err:%s)\n", name.c_str(), strerror(errno));
        close(fd);
        return XResult<SharedMemory>::FromError(XErrC::Undefined);
    }

    void* addr = mmap(NULL, statbuf.st_size, PROT_READ|PROT_WRITE, MAP_SHARED, fd, 0);
    if (!addr) {
        printf("mmap for shm: %s, with size: %ld, failed(err:%s)\n", name.c_str(), statbuf.st_size, strerror(errno));
        close(fd);
        return XResult<SharedMemory>::FromError(XErrC::InsufficientReousces);
    }

    SharedMemory_t* sbuf = new SharedMemory_t;
    sbuf->fd = fd;
    sbuf->buf = addr;
    sbuf->size = statbuf.st_size;
    sbuf->name = name;

    return sbuf;
}

XResult<void*>  ShmBuffer(const SharedMemory shm) {
    if (shm == nullptr) {
        return XResult<void*>::FromError(XErrC::InvalidArgument);
    }
    SharedMemory_t* sbuf = reinterpret_cast<SharedMemory_t*>(shm);
    if ((sbuf->fd == -1) || (sbuf->buf == nullptr)) {
        return XResult<void*>::FromError(XErrC::InvalidStatus);
    }

    return sbuf->buf;
}

XResult<uint32_t>  ShmBufferSize(const SharedMemory shm) {
    if (shm == nullptr) {
        return XResult<uint32_t>::FromError(XErrC::InvalidArgument);
    }
    SharedMemory_t* sbuf = reinterpret_cast<SharedMemory_t*>(shm);
    if ((sbuf->fd == -1) || (sbuf->buf == nullptr)) {
        return XResult<uint32_t>::FromError(XErrC::InvalidStatus);
    } 

    return sbuf->size;   
}

XResult<void> ShmFree(SharedMemory shm) {
    if (shm == nullptr) {
        return XResult<void>::FromError(XErrC::InvalidArgument);
    }
    SharedMemory_t* sbuf = reinterpret_cast<SharedMemory_t*>(shm);
    if ((sbuf->fd == -1) || (sbuf->buf == nullptr)) {
        return XResult<void>::FromError(XErrC::InvalidStatus);
    }

    munmap(sbuf->buf, sbuf->size);
    close(sbuf->fd);
    if (sbuf->is_owner) {
        shm_unlink(sbuf->name.c_str());
    }
    delete sbuf;
    return { };
}

} //namespace shmipc::core